/**
 * Yoyo validate 验证器
 * author:Yoyo
 * qq:1294858802
 * versions:0.0.1
 * 主要实现验证器的功能实现等(仿ThinkPHP 的 验证器)
 */


 class validate {

    /**
     * 内置规则
     * ${1},${2}对应数组替换
     */
    #initRules = {
        require: '^[\\d\\D]+$',//是否必须有
        number: '^(\\-|\\+)?\\d+(\\.\\d+)?$',//是否是数字 正数、负数、和小数
        int: '^[0-9]+$',//正整数
        boolean: '^(false|true|0|1)$',//是否是布尔值
        float: '^(-?\\d+)(\.\\d+)?$',//是否是浮点小数
        email: '^[A-Za-z\\d]+([-_.][A-Za-z\\d]+)*@([A-Za-z\\d]+[-.])+[A-Za-z\\d]{2,4}$',//邮箱
        zh: '^[\\u4e00-\\u9fa5]+$',//判断是否是中文
        idCard: '^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$',//效验18位身份证
        ip: '\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b',//效验ipV4
        numberEn: '^[A-Za-z0-9]+$',//字母和数字
        date: '^\\d{4}-\\d{1,2}-\\d{1,2}',//日期格式
        min: '^[\\d\\D]{${1},}$',//最小
        max: '^[\\d\\D]{0,${1}}$',//最大
        between: '^[\\d\\D]{${1},${2}}$',//区间
    };

    /**
     * 用逗号分隔的区间字段
     */
    #areaValue = ['between'];

    /**
     * 内置提示
     * {name}
     * ${1},${2}对应数组替换
     */
    #initMessage = {
        require: '{name} is require',//是必须的
        number: '{name} is number',//是数字
        int: '{name} is integer',//是整数
        boolean: '{name} is boolean',//是布尔值
        float: '{name} is float',//是浮点数
        email: '{name} is email',//是邮箱
        zh: '{name} is Chinese',//是中文
        idCard: '{name} is identity card',//是身份证号码
        ip: '{name} is IP address v4',//是ip
        numberEn: '{name} is Combination of letters and numbers',
        date: '{name} is Date',
        min: '{name} contains at least ${1} characters',//最小
        max: '{name} contains a maximum of ${1} characters',//最大
        between: '{name} must range from ${1} to ${2}',//区间
    };

    /**
     * 规则语法树
     */
    #ruleTree = {};


    /**
     * 修整规则到语法树
     * @param {Object} rules 
     */
    #trimRule(rules) {
        let rulesObject = {};
        if (this.#isTypeof(rules) == 'object' && Object.keys(rules).length > 0) {
            //当规则存在并且有数据的时候进行修整到语法树
            let ruleName = Object.keys(rules);
            ruleName.forEach(field => {
                if (this.#isTypeof(rules[field]) == 'string') {
                    // 字符串解析
                    let ruleArr = rules[field].match(/[^\|]+/g);//结构成数组
                    let newarr = ruleArr.map(v => v.match(/[^:]+/g));
                    let ketObj = {};
                    newarr.forEach(v => {
                        if (this.#areaValue.includes(v[0])) {
                            let arr = v[1] ?? '';
                            arr = String(arr).match(/[^,]+/g);
                            ketObj[v[0]] = arr.length < 2 ? null : arr;//不正确返回null 不进行处理
                        }
                        else {
                            ketObj[v[0]] = v[1] ?? true;
                        }
                    });//将数组结构到语法树
                    rulesObject[field] = ketObj;
                } else if (this.#isTypeof(rules[field]) == 'object') {
                    let keyArr = Object.keys(rules[field]);
                    let value = rules[field];
                    keyArr.forEach(v => {
                        if (this.#areaValue.includes(v) && this.#isTypeof(value[v]) != 'array') {
                            let arr = value[v] ?? '';
                            arr = String(arr).match(/[^,]+/g);
                            value[v] = arr.length < 2 ? null : arr;//不正确返回null 不进行处理
                        }
                    });
                    rulesObject[field] = value;
                }
            });
        }

        return rulesObject;
    }


    /**
         * 判断类型 
         * @param {Any} data 
         * @returns {string} 小写
         */
    #isTypeof(data) {
        let type = Object.prototype.toString.call(data);
        return type.replace(/^\[object (.+)\]$/, '$1').toLowerCase();
    }

    /**
     * 检查参数
     * @param {Object} data 一个对象保护需要验证的字段和值
     */
    check(data) {
        if (this.#isTypeof(data) != 'object') {
            throw new Error('Please pass in the correct object to check');
        }
        for (let field in this.#ruleTree) {
            let ruledata = this.#ruleTree[field];
            let dataKeyArr = Object.keys(data);
            if (!dataKeyArr.includes(field)) {
                if (!ruledata.require) continue;//字段不存在 并且没有require强制需要就跳过
                throw new Error(this.#msgThrow(field, 'require', ''));
            }
            // 遍历字段的对象
            for (let ruleKey in ruledata) {
                if (ruledata[ruleKey]) {
                    //rule是正确的才进行过滤
                    //对正则进行替换
                    let NewRuleInit = (() => this.#initRules[ruleKey])();//!!!保证每次匹配一个字段的时候都是最开始初始化的
                    if (!NewRuleInit) {
                        if (ruleKey != 'regular') {
                            if (this.#isTypeof(this[ruleKey]) != 'function') {
                                continue;//找不到内置的规则(是错误的直接跳过)(后期增加调用自定义方法的)
                            }
                            //自定义数组验证
                            let result = this[ruleKey](data[field], field, data);//调用用户自定义方法
                            if (this.#isTypeof(result) == 'boolean') {
                                if (result === true) continue;//用户自定义方法通过验证 放行
                                //抛出用户在message定义的
                                throw new Error(this.#msgThrow(field, ruleKey, ruledata[ruleKey]));
                            } else {
                                //抛出用户自定义的错误信息
                                throw new Error(result);
                            }
                        } else {
                            //regular自定义正则表达式
                            NewRuleInit = ruledata[ruleKey];
                        }
                    } else {
                        // 只有存在 并且不是自定义正则才替换
                        if (this.#isTypeof(ruledata[ruleKey]) == 'array') {
                            ruledata[ruleKey].forEach((key, index) => {
                                let se = new RegExp('\\$\\{' + (index + 1) + '\\}', 'g');
                                NewRuleInit = NewRuleInit.replace(se, key);
                            });
                        } else {
                            NewRuleInit = NewRuleInit.replace(/\$\{1\}/g, ruledata[ruleKey]);
                        }
                    }
                    let ruleSe = new RegExp(NewRuleInit);
                    // 进行判断
                    if (!ruleSe.test(data[field])) {
                        //未通过
                        throw new Error(this.#msgThrow(field, ruleKey, ruledata[ruleKey]));
                    }
                }
            }
        }
    }

    /**
     * 筛选错误信息返回
     * @param {String} field 字段名
     * @param {String} ruleName 规则名
     */
    #msgThrow(field, ruleName, data) {
        if (this.#isTypeof(this.message) != 'object') {
            throw new Error('Please submit the correct value');
        }
        // 找指定rule错误信息,没有返回字段错误信息,内置rule错误信息

        let nativeMsg = this.message[field + '.' + ruleName];
        if (nativeMsg) {
            return this.#replaceStudio(nativeMsg, field, data);//返回字段指定rule错误信息
        } else if (this.message[field]) {
            return this.#replaceStudio(this.message[field], field, data);//返回字段错误信息
        }
        return this.#replaceStudio(this.#initMessage[ruleName], field, data);//返回内置rule错误信息
    }

    /**
     * 进行提示数据替换
     * @param {String} str 原文本
     * @param {String} name 字段名
     * @param {Array|String} data 附加数据
     * @return {String} 替换好的文本
     */
    #replaceStudio(str, name, data) {
        if (str) {
            str = str.replace(/\{name\}/g, name);
            if (this.#isTypeof(data) == 'array') {
                data.forEach((key, index) => {
                    let se = new RegExp('\\$\\{' + (index + 1) + '\\}', 'g');
                    str = str.replace(se, key);
                });
            } else {
                str = str.replace(/\$\{1\}/g, data);
            }
        } else {
            str = `${name} failed the validation`;
        }
        return str;
    }

    /**
     * 场景需要字段(对已有的规则进行删减)
     * @param {Array} fields 字段数组集合
     */
    only(fields) {
        if (Array.isArray(fields)) {
            let ruleTree = {};
            fields.forEach(field => {
                ruleTree[field] = this.#ruleTree[field] ?? {};
            });
            this.#ruleTree = ruleTree;
        }
        return this;
    }

    /**
     * 向指定字段追加规则
     * @param {String} field 字段名
     * @param {String|Object} rule 规则
     */
    append(field, rule) {
        if (rule) {
            let rules = {};
            rules[field] = rule;
            let newData = this.#trimRule(rules);//修整规则到语法树
            let remKey = Object.keys(newData[field]);
            remKey.forEach(fld => {
                if (!this.#ruleTree[field]) {
                    this.#ruleTree[field] = {};//初始化
                }
                this.#ruleTree[field][fld] = newData[field][fld];//添加和替换指定的
            });
        }
        return this;
    }

    /**
     * 向指定字段移除规则
     * @param {String} field 字段名
     * @param {String|Object} rule 规则
     */
    remove(field, rule) {
        if (rule) {
            let rules = {};
            if (Array.isArray(rule)) rule = rule.join('|');//如果是数组用|分割成字符串
            rules[field] = rule;
            let newData = this.#trimRule(rules);//修整规则到语法树
            let remKey = Object.keys(newData[field]);
            remKey.forEach(fld => {
                if (this.#ruleTree[field][fld]) delete this.#ruleTree[field][fld];//移除指定的
            });
        } else {
            if (this.#ruleTree[field]) this.#ruleTree[field] = {};//设置空的
        }

        return this;
    }
    /**
     * 实现父类访问子类并实例化对象的功能
     * @param {String} scene 场景名字
     * @returns 实例化对象
     */
    static validate(scene) {
        let ceObj = new this;
        ceObj.#ruleTree = ceObj.#trimRule(ceObj.rule);//修整规则到语法树
        // 没有场景 就不用管
        if (ceObj.#isTypeof(scene) == 'string') {
            let sceneFunc = ceObj['scene' + scene];
            if (ceObj.#isTypeof(sceneFunc) == 'function') {
                //有效的场景
                try {
                    sceneFunc(ceObj);//执行场景的最终修改成需要的(内部自动修改)
                } catch (error) {
                    error.message = `['${scene}' error]:${error.message}`;
                    throw new Error(error);
                }
            }else{
                //没有方法场景去查看scene属性里面
                if(ceObj.scene[scene]&&ceObj.#isTypeof(ceObj.scene[scene])=='array'){
                    //存在属性场景并且是数组类型的
                    ceObj = ceObj.only(ceObj.scene[scene]);
                }
            }
        }
        return ceObj;
    }



}

module.exports = validate;